package com.micro.service.auth.server.login.sms;

import com.micro.service.account.share.dto.UserBaseDto;
import com.micro.service.account.share.service.UserBaseService;
import com.micro.service.auth.server.exception.AccountCenterException;
import com.micro.service.auth.server.exception.ValidateCodeException;
import com.micro.service.auth.server.redis.RedisManager;
import com.micro.service.common.dto.ResultDTO;
import com.micro.service.common.enums.SmsCodePrefixEnum;
import com.micro.service.common.enums.UserStatusEnum;
import com.micro.service.common.utils.HttprequestUtils;
import com.micro.service.common.utils.ImeiUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

/**
 * @author xiucai
 *
 */
@Slf4j
public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

	private String mobileParameter = "mobile";
	private boolean postOnly = true;

	@Autowired
	private UserBaseService userBaseService;


	private RedisManager vcodeManager;
	// ~ Constructors
	// ===================================================================================================

	public SmsCodeAuthenticationFilter() {
		super(new AntPathRequestMatcher("/sms/login", "POST"));
	}

	// ~ Methods
	// ========================================================================================================

	@Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException {
		if (postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
		}
		Map<String,Object> map = HttprequestUtils.getRequestMap(request);
		if(CollectionUtils.isEmpty(map)){
			throw new ValidateCodeException("短信登陆参数不能为空！");
		}
		String mobile = ( map.get(mobileParameter)==null|| map.get(mobileParameter).equals(""))?null:map.get(mobileParameter).toString();
		if (mobile == null) {
			throw new ValidateCodeException("短信登陆手机号不能为空！");
		}

		mobile = mobile.trim();
		String code = (map.get("smsCode")==null||map.get("smsCode").equals(""))?null:map.get("smsCode").toString();
		if(code == null){
			throw new ValidateCodeException("验证码不能为空");
		}
		//校验验证码
		boolean checkResult = checkMobile(mobile,code);
		if(!checkResult){
			throw new ValidateCodeException("验证码无效，请稍后重试");
		}
		//检查手机号是否注册，如未注册则注册
		String client_imei = ImeiUtils.getImeiFromHeaders(request);
		if(StringUtils.isBlank(client_imei)){
			throw new ValidateCodeException("imei号不能为空");
		}
		ResultDTO<UserBaseDto> userBaseDto = userBaseService.checkAndRegisterByMobile(mobile,client_imei);
		if(userBaseDto.getData() != null && UserStatusEnum.IS_DEL.getStatus().equals(userBaseDto.getData().getStatus())){
			log.error("用户已注销，userBaseDto:{}",userBaseDto.getData());
			throw new AccountCenterException("用户已注销");
		}
		SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);

		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);

		return this.getAuthenticationManager().authenticate(authRequest);
	}

	//校验验证码
	private boolean checkMobile(String mobile,String codeInRequest) {
		try{


			Object codeInRedis = vcodeManager.getVcode(SmsCodePrefixEnum.SMS_CODE_PREFIX.getType()+mobile);
			if(codeInRedis == null){
				throw new ValidateCodeException("验证码已失效");
			}
			if(!StringUtils.equals(codeInRedis.toString(),codeInRequest)){
				throw new ValidateCodeException("验证码不正确");
			}
			vcodeManager.removeVcode(SmsCodePrefixEnum.SMS_CODE_PREFIX.getType()+mobile);
		}catch (RedisConnectionFailureException e){
			log.error("校验验证码获取newSys的redis连接异常");
			return false;
		}

		return true;
	}


	/**
	 * Provided so that subclasses may configure what is put into the
	 * authentication request's details property.
	 *
	 * @param request
	 *            that an authentication request is being created for
	 * @param authRequest
	 *            the authentication request object that should have its details
	 *            set
	 */
	protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
		authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
	}

	/**
	 * Sets the parameter name which will be used to obtain the username from
	 * the login request.
	 *
	 * @param usernameParameter
	 *            the parameter name. Defaults to "username".
	 */
	public void setMobileParameter(String usernameParameter) {
		Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
		this.mobileParameter = usernameParameter;
	}


	/**
	 * Defines whether only HTTP POST requests will be allowed by this filter.
	 * If set to true, and an authentication request is received which is not a
	 * POST request, an exception will be raised immediately and authentication
	 * will not be attempted. The <tt>unsuccessfulAuthentication()</tt> method
	 * will be called as if handling a failed authentication.
	 * <p>
	 * Defaults to <tt>true</tt> but may be overridden by subclasses.
	 */
	public void setPostOnly(boolean postOnly) {
		this.postOnly = postOnly;
	}

	public final String getMobileParameter() {
		return mobileParameter;
	}

	public RedisManager getVcodeManager() {
		return vcodeManager;
	}

	public void setVcodeManager(RedisManager vcodeManager) {
		this.vcodeManager = vcodeManager;
	}

	public UserBaseService getUserBaseService() {
		return userBaseService;
	}

	public void setUserBaseService(UserBaseService userBaseService) {
		this.userBaseService = userBaseService;
	}
}
